package fag.core.game;

import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import fag.core.collision.Hitbox;
import fag.core.geometry.Rectangle2D;
import fag.core.geometry.Vector2D;
import fag.core.model.Animation;
import fag.core.model.Model;
import fag.core.model.Sprite;

/**
 * Classe des objets de scène.
 * @author lumz
 */
public class StandardDoodad extends AbstractDoodad implements Transformable {

	private Model model = null;
	private Animation animation = null;
	
	private boolean isBoundsCached = false;
	private boolean isHitboxCached = false;
	
	private Rectangle2D boundsCache = null;
	private Hitbox hitboxCache = null;
	
	/**
	 * Constructeur par défaut.
	 * @param model Le modèle associé au doodad.
	 */
	public StandardDoodad(Model model) {
		this.model = model;
		animation = model.createDefaultAnimation();
	}
	
	/**
	 * Accesseur de l'animation courante.
	 * @return Le facteur de vitesse de l'animation.
	 */
	public Animation getAnimation() {
		return animation;
	}
	
	@Override
	public Rectangle2D getBounds() {
		
		// Cache hit
		if(isBoundsCached)
			return boundsCache.clone();
		
		// Création du cache
		synchronized(transformation) {
			boundsCache = animation.getCurrentBounds().transform(transformation).getBounds();
		}
			
		isBoundsCached = true;
		
		return boundsCache.clone();
	}
	
	@Override
	public Hitbox getHitbox() {
		
		// Cache hitbox
		if(isHitboxCached)
			return hitboxCache.clone();
		
		// Création du cache
		synchronized(transformation) {
			hitboxCache = animation.getCurrentHitbox().transform(transformation);
		}
		
		isHitboxCached = true;
		
		return hitboxCache.clone();
	}

	@Override
	public void rotate(double angle, double x, double y, Referential referential) {
		
		isBoundsCached = false;
		isHitboxCached = false;
		
		Rectangle2D bounds = null;
		Rectangle2D oldBounds = null;
		Rectangle2D newBounds = null; 
		
		synchronized(animation) {
			bounds = animation.getCurrentSprite().getBounds();
		}
		
		synchronized(transformation) {
			
			oldBounds = bounds.transform(transformation).getBounds();
		
			AffineTransform rotation = AffineTransform.getRotateInstance(angle, x, y);
			
			switch(referential) {
			case ABSOLUTE:
				transformation.preConcatenate(rotation);
				break;
				
			case RELATIVE:
				transformation.concatenate(rotation);
				break;
			}
			
			newBounds = bounds.transform(transformation).getBounds();
		}
		
		if(scene != null) {
			scene.addDirtyRegion(oldBounds);
			scene.addDirtyRegion(newBounds);
		}
	}

	@Override
	public void scale(double sx, double sy, double ax, double ay, Referential referential) {

		isBoundsCached = false;
		isHitboxCached = false;
		
		Rectangle2D bounds = null;
		Rectangle2D oldBounds = null;
		Rectangle2D newBounds = null; 
		
		synchronized(animation) {
			bounds = animation.getCurrentSprite().getBounds();
		}
		
		synchronized(transformation) {
			
			oldBounds = bounds.transform(transformation).getBounds();
			
			AffineTransform scaling = AffineTransform.getScaleInstance(sx, sy);
			
			switch(referential) {
			case ABSOLUTE:
				transformation.preConcatenate(AffineTransform.getTranslateInstance(-ax, -ay));
				transformation.preConcatenate(scaling);
				transformation.preConcatenate(AffineTransform.getTranslateInstance(ax, ay));
				break;
				
			case RELATIVE:
				transformation.concatenate(AffineTransform.getTranslateInstance(-ax, -ay));
				transformation.concatenate(scaling);
				transformation.concatenate(AffineTransform.getTranslateInstance(ax, ay));
				break;
			}
			
			newBounds = bounds.transform(transformation).getBounds();
		}
		
		if(scene != null) {
			scene.addDirtyRegion(oldBounds);
			scene.addDirtyRegion(newBounds);
		}
	}
	
	/**
	 * Fixer l'animation courante.
	 * @param name Le nom de l'animation à jouer.
	 */
	public void setAnimation(String name) {
		
		synchronized(animation) {
		
			synchronized(transformation) {
				
				if(scene != null)
					scene.addDirtyRegion(animation.getCurrentSprite().getBounds().transform(transformation).getBounds());
			
				animation = model.createAnimation(name);
			}
		}
	}
	
	@Override
	public void setRotation(double angle) {
		
		isBoundsCached = false;
		isHitboxCached = false;
		
		Rectangle2D bounds = null;
		Rectangle2D oldBounds = null;
		Rectangle2D newBounds = null;
		
		synchronized(animation) {
			bounds = animation.getCurrentSprite().getBounds();
		}
		
		synchronized(transformation) {
			
			double[] flatmatrix = new double[6];
			transformation.getMatrix(flatmatrix);
			
			double cos = Math.cos(angle);
			double sin = Math.sin(angle);
			
			oldBounds = bounds.transform(this.transformation).getBounds();
			transformation.setTransform(cos, sin, -sin, cos, flatmatrix[4], flatmatrix[5]);
			newBounds = bounds.transform(this.transformation).getBounds();
		}
		
		if(scene != null) {
			scene.addDirtyRegion(oldBounds);
			scene.addDirtyRegion(newBounds);
		}
	}
	
	@Override
	public void setTransformation(AffineTransform transformation) {
		
		isBoundsCached = false;
		isHitboxCached = false;
		
		Rectangle2D bounds = null;
		Rectangle2D oldBounds = null;
		Rectangle2D newBounds = null;
		
		synchronized(animation) {
			bounds = animation.getCurrentSprite().getBounds();
		}
		
		synchronized(transformation) {
		
			oldBounds = bounds.transform(this.transformation).getBounds();
			this.transformation = transformation;
			newBounds = bounds.transform(this.transformation).getBounds();
		}
		
		if(scene != null) {
			scene.addDirtyRegion(oldBounds);
			scene.addDirtyRegion(newBounds);
		}
	}

	@Override
	public void setTranslation(double x, double y) {
		
		isBoundsCached = false;
		isHitboxCached = false;
		
		Rectangle2D bounds = null;
		Rectangle2D oldBounds = null;
		Rectangle2D newBounds = null;
		
		synchronized(animation) {
			bounds = animation.getCurrentSprite().getBounds();
		}
		
		synchronized(transformation) {
		
			double[] flatmatrix = new double[6];
			transformation.getMatrix(flatmatrix);
			
			oldBounds = bounds.transform(this.transformation).getBounds();
			transformation.setTransform(flatmatrix[0], flatmatrix[1], flatmatrix[2], flatmatrix[3], x, y);
			newBounds = bounds.transform(this.transformation).getBounds();
		}
		
		if(scene != null) {
			scene.addDirtyRegion(oldBounds);
			scene.addDirtyRegion(newBounds);
		}
	}
	
	@Override
	protected void draw(Graphics2D graphics2d) {
		
		// Transformation du plan pour peindre l'objet à sa position
		AffineTransform old = graphics2d.getTransform();
		
		synchronized(transformation) {
			graphics2d.transform(transformation);
		}

		Sprite sprite = null;
		synchronized(animation) {
			sprite = animation.getCurrentSprite();
		}
		
		graphics2d.drawImage(sprite.getBufferedImage(), null, (int)sprite.getOffSet().x, (int)sprite.getOffSet().y);
		
		// Restauration du contexte graphique
		graphics2d.setTransform(old);
	}
	
	@Override
	public void translate(double x, double y, Referential referential) {
		
		isBoundsCached = false;
		isHitboxCached = false;
		
		Rectangle2D bounds = null;
		Rectangle2D oldBounds = null;
		Rectangle2D newBounds = null;
		
		synchronized(animation) {
			bounds = animation.getCurrentSprite().getBounds();
		}
		
		synchronized(transformation) {
			
			oldBounds = bounds.transform(transformation).getBounds();
			
			AffineTransform translation = AffineTransform.getTranslateInstance(x, y);
			
			switch(referential) {
			case ABSOLUTE:
				transformation.preConcatenate(translation);
				break;
				
			case RELATIVE:
				transformation.concatenate(translation);
				break;
			}
			
			newBounds = bounds.transform(transformation).getBounds();
		}
		
		if(scene != null) {
			scene.addDirtyRegion(oldBounds);
			scene.addDirtyRegion(newBounds);
		}
	}
	
	@Override
	protected void forward(double dt) {
		
		synchronized(animation) {
			
			if(animation.frameChanged()) {
				
				synchronized(transformation) {
					scene.addDirtyRegion(animation.getCurrentSprite().getBounds().transform(transformation).getBounds());
				}
			}
				
			animation.forward(dt);
		}
	}
	
	@Override
	protected void setScene(Scene scene) {
		
		// Ajout
		if(scene != null) {
			
			this.scene = scene;
			
			synchronized(animation) {
				
				synchronized(transformation) {
					scene.addDirtyRegion(animation.getCurrentSprite().getBounds().transform(transformation).getBounds());
				}
			}
		}
		// Suppression
		else {
			
			Scene tmp = this.scene;
			this.scene = null;
			
			synchronized(animation) {
				
				synchronized(transformation) {
					tmp.addDirtyRegion(animation.getCurrentSprite().getBounds().transform(transformation).getBounds());
				}
			}
		}
	}
}
